package com.xian.minespring.context;

import com.xian.minespring.annotation.*;
import com.xian.minespring.exception.*;
import com.xian.minespring.io.PropertyResolver;
import com.xian.minespring.io.ResourceResolver;
import com.xian.minespring.utils.ClassUtils;
import jakarta.annotation.Nullable;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.*;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author zhan
 * @version 2024/1/2
 */
public class AnnotationConfigApplicationContext implements ConfigurableApplicationContext {

    protected final Logger logger = LoggerFactory.getLogger(getClass());
    protected final PropertyResolver propertyResolver;
    protected final Map<String, BeanDefinition> beans;

    private Set<String> creatingBeanNames;
    private List<BeanPostProcessor> beanPostProcessors = new ArrayList<>();

    /**
     * 初始化context
     *
     * @param configClass      启动类
     * @param propertyResolver 属性配置
     */
    public AnnotationConfigApplicationContext(Class<?> configClass, PropertyResolver propertyResolver) {
        // 设置工具类
        ApplicationContextUtils.setApplication(this);

        // 属性配置
        this.propertyResolver = propertyResolver;
        // 扫描获取所有Bean的Class类型:
        Set<String> beanClassNames = scanForClassNames(configClass);
        // pick出需要注入context的class创建Bean的定义:
        this.beans = createBeanDefinitions(beanClassNames);
        // 创建实例 用来check循环依赖
        this.creatingBeanNames = new HashSet<>();

        // 过滤出@Configuration类型的BeanDefinition，创建实例: [先创建强依赖的bean]
        List<BeanDefinition> configurationDefs = this.beans.values().stream().filter(this::isConfigurationDefinition).sorted().toList();
        logger.atDebug().log("待创建@Configuration类型的BeanDef:{}", configurationDefs);
        configurationDefs.forEach(this::createBeanAsEarlySingleton);

        // 过滤出BeanPostProcessor的实现类，先于被代理bean创建
        List<BeanPostProcessor> beanPostProcessors = this.beans.values().stream()
                .filter(def -> BeanPostProcessor.class.isAssignableFrom(def.getBeanClass()))
                .sorted().map(def -> (BeanPostProcessor) createBeanAsEarlySingleton(def)).toList();
        logger.atDebug().log("代理beanPostProcessors:{}", beanPostProcessors);
        this.beanPostProcessors = beanPostProcessors;

        // 创建其他普通Bean 过滤出instance==null的BeanDefinition:
        this.beans.values().stream().filter(def -> def.getInstance() == null).sorted().forEach(def -> {
            if (def.getInstance() == null) {
                createBeanAsEarlySingleton(def);
            }
        });

        // 通过字段和set方法注入依赖:
        this.beans.values().forEach(this::injectBean);

        // 调用init方法:
        this.beans.values().forEach(this::initBean);
    }

    /**
     * 注入依赖
     */
    private void injectBean(BeanDefinition def) {
        // 获取Bean实例，或被代理的原始实例:
        Object beanInstance = getProxiedInstance(def);
        try {
            injectProperties(def, def.getBeanClass(), beanInstance);
        } catch (Exception e) {
            throw new BeanCreationException(e);
        }
    }

    /**
     * 找到原始元素被代理类
     * @param def 代理类df
     * @return originBeanInstance
     */
    private Object getProxiedInstance(BeanDefinition def) {
        Object beanInstance = def.getInstance();
        // 如果Proxy改变了原始Bean，又希望注入到原始Bean，则由BeanPostProcessor指定原始Bean:
        List<BeanPostProcessor> reversedBeanPostProcessors = new ArrayList<>(this.beanPostProcessors);
        // 反转的意义，加载代理的时候是按照order顺序一层一层代理的，查找需要倒过来不让会查询不到
        Collections.reverse(reversedBeanPostProcessors);
        for (BeanPostProcessor beanPostProcessor : reversedBeanPostProcessors) {
            Object restoredInstance = beanPostProcessor.postProcessOnSetProperty(beanInstance, def.getName());
            if (restoredInstance != beanInstance) {
                beanInstance = restoredInstance;
            }
        }
        return beanInstance;
    }

    /**
     * 注入属性
     */
    private void injectProperties(BeanDefinition def, Class<?> beanClass, Object instance) throws ReflectiveOperationException {

        for (Field field : beanClass.getDeclaredFields()) {
            tryInjectProperties(def, beanClass, instance, field);
        }

        for (Method declaredMethod : beanClass.getDeclaredMethods()) {
            tryInjectProperties(def, beanClass, instance, declaredMethod);
        }

        // 父类查询field和method
        Class<?> superclass = beanClass.getSuperclass();
        if (superclass != null) {
            injectProperties(def, superclass, instance);
        }
    }

    private void tryInjectProperties(BeanDefinition def, Class<?> beanClass, Object instance, AccessibleObject accObj) throws ReflectiveOperationException {
        Value value = accObj.getAnnotation(Value.class);
        Autowired autowired = accObj.getAnnotation(Autowired.class);
        if (value == null && autowired == null) {
            return;
        }


        Field field = null;
        Method method = null;

        if (accObj instanceof Field f) {
            checkFieldOrMethod(f);
            f.setAccessible(true);
            field = f;
        }

        if (accObj instanceof Method m) {
            checkFieldOrMethod(m);
            if (m.getParameters().length != 1) {
                // 不是setter函数
                throw new BeanDefinitionException(
                        String.format("Cannot inject a non-setter method %s for bean '%s': %s", m.getName(), def.getName(), def.getBeanClass().getName()));
            }
            m.setAccessible(true);
            method = m;
        }

        String accessibleName = field != null ? field.getName() : method.getName();
        Class<?> accessibleType = field != null ? field.getType() : method.getParameterTypes()[0];

        // @Autowired @Value 不能同时使用
        if (value != null && autowired != null) {
            throw new BeanCreationException(String.format("Cannot specify both @Autowired and @Value when inject %s.%s for bean '%s': %s",
                    beanClass.getSimpleName(), accessibleName, def.getName(), def.getBeanClass().getName()));
        }

        // @Value注入:
        if (value != null) {
            Object propValue = this.propertyResolver.getRequiredProperty(value.value(), accessibleType);
            if (field != null) {
                logger.atDebug().log("Field injection: {}.{} = {}", def.getBeanClass().getName(), accessibleName, propValue);
                field.set(instance, propValue);
            }
            if (method != null) {
                logger.atDebug().log("Method injection: {}.{} ({})", def.getBeanClass().getName(), accessibleName, propValue);
                method.invoke(instance, propValue);
            }
        }

        // @Autowired注入:
        if (autowired != null) {
            String name = autowired.name();
            boolean required = autowired.value();
            Object depends = name.isEmpty() ? findBean(accessibleType) : findBean(name, accessibleType);
            if (required && depends == null) {
                throw new UnsatisfiedDependencyException(String.format("Dependency bean not found when inject %s.%s for bean '%s': %s", beanClass.getSimpleName(),
                        accessibleName, def.getName(), def.getBeanClass().getName()));
            }
            if (depends != null) {
                if (field != null) {
                    logger.atDebug().log("Field injection: {}.{} = {}", def.getBeanClass().getName(), accessibleName, depends);
                    field.set(instance, depends);
                }
                if (method != null) {
                    logger.atDebug().log("Mield injection: {}.{} ({})", def.getBeanClass().getName(), accessibleName, depends);
                    method.invoke(instance, depends);
                }
            }
        }

    }

    /**
     * 检查字段和方法否可访问可修改
     * @param member method和field的父类
     */
    private void checkFieldOrMethod(Member member) {
        int modifiers = member.getModifiers();

        if (Modifier.isStatic(modifiers)) {
            throw new BeanDefinitionException("Cannot inject static field: " + member);
        }

        if (Modifier.isFinal(modifiers)) {
            if (member instanceof Field field) {
                throw new BeanDefinitionException("Cannot inject final field: " + field);
            }
            if (member instanceof Method method) {
                logger.warn(
                        "Inject final method should be careful because it is not called on target bean when bean is proxied and may cause NullPointerException.");

            }
        }
    }


    /**
     * 调用初始化方法
     */
    private void initBean(BeanDefinition def) {
        try {
            callMethod(def.getInstance(), def.getInitMethod(), def.getInitMethodName());
        } catch (Exception e) {
            throw new BeanCreationException(e);
        }
    }

    private void callMethod(Object instance, Method initMethod, String initMethodName) {
        if (initMethod != null) {
            try {
                initMethod.invoke(instance);
            } catch (ReflectiveOperationException e) {
                throw new BeanCreationException(e);
            }
        } else if (initMethodName != null) {
            Method namedMethod = ClassUtils.getNamedMethod(instance.getClass(), initMethodName);
            try {
                namedMethod.invoke(instance);
            } catch (ReflectiveOperationException e) {
                throw new BeanCreationException(e);
            }
        }
    }

    @Override
    // 创建一个Bean，但不进行字段和方法级别的注入。如果创建的Bean不是Configuration，则在构造方法/工厂方法中注入的依赖Bean会自动创建
    public Object createBeanAsEarlySingleton(BeanDefinition def) {
        if (!this.creatingBeanNames.add(def.getName())) {
            // 检测到重复创建Bean导致的循环依赖:
            throw new UnsatisfiedDependencyException();
        }
        // 获取构造方法或者工场方法创建baen
        Executable createFn = def.getFactoryName() == null ? def.getConstructor() : def.getFactoryMethod();

        // 执行方法参数 [强依赖的注入]
        Parameter[] parameters = createFn.getParameters();
        Object[] args = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            // 强依赖的bean
            Autowired autowired = parameter.getAnnotation(Autowired.class);
            if (autowired != null && this.creatingBeanNames.contains(autowired.name())) {
                throw new UnsatisfiedDependencyException();
            }
            // 属性
            Value value = parameter.getAnnotation(Value.class);

            Class<?> type = parameter.getType();
            if (value != null) {
                // 是属性,根据name和type获取属性
                args[i] = this.propertyResolver.getRequiredProperty(value.value(), type);
            } else if (autowired != null) {
                // 是bean
                BeanDefinition beanDefinition = autowired.name().isEmpty() ? findBeanDefinition(type) : findBeanDefinition(autowired.name());
                if (beanDefinition == null) {
                    throw new BeanCreationException(String.format("Exception when autowired parameters of create bean '%s': %s", def.getName(), def.getBeanClass().getName()));
                }
                Object autowiredBeanInstance = beanDefinition.getInstance();
                if (autowiredBeanInstance == null) {
                    autowiredBeanInstance = createBeanAsEarlySingleton(beanDefinition);
                }
                args[i] = autowiredBeanInstance;
            } else {
                throw new BeanCreationException(String.format("Exception when get parameters of create bean '%s': %s", def.getName(), def.getBeanClass().getName()));
            }
        }
        // 创建方法和参数都准备好了
        Object instance = null;
        if (def.getConstructor() != null) {
            // 构造方法创建
            try {
                instance = def.getConstructor().newInstance(args);
            } catch (Exception e) {
                throw new BeanCreationException(String.format("Exception when create bean '%s': %s", def.getName(), def.getBeanClass().getName()), e);
            }
        } else {
            // 工厂方法创建
            Object configInstance = getBean(def.getFactoryName());
            try {
                instance = def.getFactoryMethod().invoke(configInstance, args);
            } catch (Exception e) {
                throw new BeanCreationException(String.format("Exception when create bean '%s': %s", def.getName(), def.getBeanClass().getName()), e);
            }
        }

        def.setInstance(instance);

        // 调用BeanPostProcessor处理Bean:
        for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {
            Object processed = beanPostProcessor.postProcessBeforeInitialization(def.getInstance(), def.getName());
            // 如果一个BeanPostProcessor替换了原始Bean，则更新Bean的引用:
            if (def.getInstance() != processed) {
                def.setInstance(processed);
            }
        }

        return def.getInstance();
    }

    /**
     * 是否有Configuration注解
     */
    boolean isConfigurationDefinition(BeanDefinition def) {
        return ClassUtils.findAnnotation(def.getBeanClass(), Configuration.class) != null;
    }


    /**
     * 创建beanDefinition
     * 通过Class.forName(beanClassName)获取类的实例，获取实例的构造方法、注解信息等创建BeanDefinition
     *
     * @param beanClassNames 类全名
     * @return beanDefinition
     */
    private Map<String, BeanDefinition> createBeanDefinitions(Set<String> beanClassNames) {
        Map<String, BeanDefinition> defs = new HashMap<>();
        beanClassNames.forEach(beanClassName -> {
            Class<?> clazz = null;

            try {
                clazz = Class.forName(beanClassName);
            } catch (ClassNotFoundException e) {
                throw new BeanCreationException(e);
            }
            // 是否标注@Component? 带递归的查询兼容@Configuration内部的@Component注解
            Component component = ClassUtils.findAnnotation(clazz, Component.class);
            if (component != null) {
                // 获取Bean的名称:
                String beanName = ClassUtils.getBeanName(clazz);
                var def = new BeanDefinition(beanName, clazz, getSuitableConstructor(clazz), getOrder(clazz), clazz.isAnnotationPresent(Primary.class),
                        // named init / destroy method:
                        null, null,
                        // init method:
                        ClassUtils.findAnnotationMethod(clazz, PostConstruct.class),
                        // destroy method:
                        ClassUtils.findAnnotationMethod(clazz, PreDestroy.class));
                logger.atDebug().log("define bean: {}", def);
                addBeanDefinitions(defs, def);
                // 查找是否有@Configuration:
                Configuration configuration = ClassUtils.findAnnotation(clazz, Configuration.class);
                if (configuration != null) {
                    // 查找@Bean方法:
                    scanFactoryMethods(beanName, clazz, defs);
                }
            }

        });
        return defs;
    }

    /**
     * Scan factory method that annotated with @Bean:
     * <code>
     * &#064;Configuration
     * public class Hello {
     * <p>
     * &#064;Bean
     * ZoneId createZone() {
     * return ZoneId.of("Z");
     * }
     * }
     * </code>
     */
    void scanFactoryMethods(String factoryBeanName, Class<?> clazz, Map<String, BeanDefinition> defs) {
        // @configuration 注解下所有的带@bean注解声明方法
        for (Method method : clazz.getDeclaredMethods()) {
            Bean bean = method.getAnnotation(Bean.class);
            if (bean != null) {
                // 确保后续反射的执行
                int mod = method.getModifiers();
                if (Modifier.isAbstract(mod)) {
                    throw new BeanDefinitionException("@Bean method " + clazz.getName() + "." + method.getName() + " must not be abstract.");
                }
                if (Modifier.isFinal(mod)) {
                    throw new BeanDefinitionException("@Bean method " + clazz.getName() + "." + method.getName() + " must not be final.");
                }
                if (Modifier.isPrivate(mod)) {
                    throw new BeanDefinitionException("@Bean method " + clazz.getName() + "." + method.getName() + " must not be private.");
                }
                // 方法返回值检查
                Class<?> beanClass = method.getReturnType();
                if (beanClass.isPrimitive()) {
                    throw new BeanDefinitionException("@Bean method " + clazz.getName() + "." + method.getName() + " must not return primitive type.");
                }
                if (beanClass == void.class || beanClass == Void.class) {
                    throw new BeanDefinitionException("@Bean method " + clazz.getName() + "." + method.getName() + " must not return void.");
                }
                var def = new BeanDefinition(ClassUtils.getBeanName(method), beanClass, factoryBeanName, method, getOrder(method),
                        method.isAnnotationPresent(Primary.class),
                        // init method:
                        bean.initMethod().isEmpty() ? null : bean.initMethod(),
                        // destroy method:
                        bean.destroyMethod().isEmpty() ? null : bean.destroyMethod(),
                        // @PostConstruct / @PreDestroy method:
                        null, null);
                addBeanDefinitions(defs, def);
                logger.atDebug().log("define bean: {}", def);
            }
        }
    }


    /**
     * Check and add bean definitions.
     */
    void addBeanDefinitions(Map<String, BeanDefinition> defs, BeanDefinition def) {
        if (defs.put(def.getName(), def) != null) {
            throw new BeanDefinitionException("Duplicate bean name: " + def.getName());
        }
    }

    private int getOrder(Class<?> clazz) {
        Order order = clazz.getAnnotation(Order.class);
        return order == null ? Integer.MAX_VALUE : order.value();
    }

    private int getOrder(Method method) {
        Order order = method.getAnnotation(Order.class);
        return order == null ? Integer.MAX_VALUE : order.value();
    }

    /**
     * Get public constructor or non-public constructor as fallback.
     */
    Constructor<?> getSuitableConstructor(Class<?> clazz) {
        // 获取公共的构造函数 public
        Constructor<?>[] cons = clazz.getConstructors();
        if (cons.length == 0) {
            // 获取当前类声明的所有构造函数
            cons = clazz.getDeclaredConstructors();
            if (cons.length != 1) {
                // 构造函数太多会让程序不知道调用哪个
                throw new BeanDefinitionException("More than one constructor found in class " + clazz.getName() + ".");
            }
        }
        if (cons.length != 1) {
            // 构造函数太多会让程序不知道调用哪个
            throw new BeanDefinitionException("More than one public constructor found in class " + clazz.getName() + ".");
        }
        return cons[0];
    }

    /**
     * 扫描启动类
     *
     * @param configClass Application.class
     * @return 扫描到的类集合
     */
    private Set<String> scanForClassNames(Class<?> configClass) {
        // 获取@ComponentScan注解:
        ComponentScan scan = ClassUtils.findAnnotation(configClass, ComponentScan.class);
        // 获取注解配置的package名字,未配置则默认当前类所在包:
        String[] scanPackages = scan == null || scan.value().length == 0 ? new String[]{configClass.getPackage().getName()} : scan.value();
        Set<String> classNameSet = new HashSet<>();
        // 依次扫描所有包:
        for (String pkg : scanPackages) {
            logger.atDebug().log("scan package: {}", pkg);
            // 扫描一个包:
            var rr = new ResourceResolver(pkg);
            List<String> classList = rr.scan(res -> {
                // 遇到以.class结尾的文件，就将其转换为Class全名:
                String name = res.name();
                if (name.endsWith(".class")) {
                    return name.substring(0, name.length() - 6).replace("/", ".").replace("\\", ".");
                }
                return null;
            });
            // 扫描结果添加到Set:
            classNameSet.addAll(classList);
        }

        // 获取Import注解
        Import importConfig = configClass.getAnnotation(Import.class);
        if (importConfig != null) {
            for (Class<?> importConfigClass : importConfig.value()) {
                String importClassName = importConfigClass.getName();
                classNameSet.add(importClassName);
            }
        }
        return classNameSet;
    }


    @Override
    public boolean containsBean(String name) {
        return this.beans.containsKey(name);
    }

    /**
     * 通过Name查找Bean，不存在时抛出NoSuchBeanDefinitionException
     */
    @Override
    @SuppressWarnings("unchecked")
    public <T> T getBean(String name) {
        BeanDefinition def = this.beans.get(name);
        if (def == null) {
            throw new NoSuchBeanDefinitionException(String.format("No bean defined with name '%s'.", name));
        }
        return (T) def.getRequiredInstance();
    }

    @Override
    public <T> T getBean(Class<T> requiredType) {
        return findBean(requiredType);
    }

    @Override
    public <T> T getBean(String name, Class<T> requiredType) {
        return findBean(name, requiredType);
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> List<T> getBeans(Class<T> requiredType) {
        List<BeanDefinition> defs = findBeanDefinitions(requiredType);
        if (defs.isEmpty()) {
            return List.of();
        }
        List<T> list = new ArrayList<>(defs.size());
        for (var def : defs) {
            list.add((T) def.getRequiredInstance());
        }

        return list;
    }

    @Override
    public void close() {

    }

    @Nullable
    @SuppressWarnings("unchecked")
    protected <T> T findBean(String name, Class<T> requiredType) {
        BeanDefinition def = findBeanDefinition(name, requiredType);
        if (def == null) {
            return null;
        }
        return (T) def.getRequiredInstance();
    }

    @Nullable
    @SuppressWarnings("unchecked")
    protected <T> T findBean(Class<T> requiredType) {
        BeanDefinition def = findBeanDefinition(requiredType);
        if (def == null) {
            return null;
        }
        return (T) def.getRequiredInstance();
    }

    // 根据Name查找BeanDefinition，如果Name不存在，返回null
    @Nullable
    @Override
    public BeanDefinition findBeanDefinition(String name) {
        return this.beans.get(name);
    }

    /**
     * 通过声明类型查询BeaDefinitions
     *
     * @param type class
     * @return beanDefinition列表
     */
    @Override
    public List<BeanDefinition> findBeanDefinitions(Class<?> type) {
        return this.beans.values().stream()
                .filter(def -> type.isAssignableFrom(def.getBeanClass()))
                .sorted(Comparator.comparingInt(BeanDefinition::getOrder))
                .collect(Collectors.toList());
    }


    /**
     * 根据Name和Type查找BeanDefinition，如果Name不存在，返回null，如果Name存在，但Type不匹配，抛出异常。
     */
    @Nullable
    @Override
    public BeanDefinition findBeanDefinition(String name, Class<?> requiredType) {
        BeanDefinition def = findBeanDefinition(name);
        if (def == null) {
            return null;
        }
        if (!requiredType.isAssignableFrom(def.getBeanClass())) {
            throw new BeanNotOfRequiredTypeException(String.format("Autowire required type '%s' but bean '%s' has actual type '%s'.", requiredType.getName(),
                    name, def.getBeanClass().getName()));
        }
        return def;
    }


    /**
     * 通过声明类型获取一个符合条件的beanDef
     * 优先返回 @primary注解
     *
     * @param type class
     * @return beanDefinition
     */
    @Override
    public BeanDefinition findBeanDefinition(Class<?> type) {
        List<BeanDefinition> beanDefinitions = findBeanDefinitions(type);
        if (beanDefinitions.isEmpty()) {
            return null;
        }
        if (beanDefinitions.size() == 1) {
            return beanDefinitions.get(0);
        }
        List<BeanDefinition> primaryBeanDefinitions = beanDefinitions.stream().filter(BeanDefinition::isPrimary).toList();
        if (primaryBeanDefinitions.size() == 1) {
            return primaryBeanDefinitions.get(0);
        }
        if (primaryBeanDefinitions.isEmpty()) {
            throw new NoUniqueBeanDefinitionException(String.format("Multiple bean with type '%s' found, but no @Primary specified.", type.getName()));
        } else {
            throw new NoUniqueBeanDefinitionException(String.format("Multiple bean with type '%s' found, and multiple @Primary specified.", type.getName()));
        }
    }
}
